﻿using Xejen.Communication.Configuration;
using Xejen.Communication.Socket.Abstract;
using Xejen.Communication.Socket.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Xejen.Communication.Socket
{
    /// <summary>
    /// 使用异步方式<see langword="Socket.SendAsync"/>和<see langword="Socket.ReceiveAsync"/>技术向远程服务器发起请求
    /// </summary>
    /// <creator>marc</creator>
    internal class EventInvoker : ISocketInvoker
    {
        private readonly ConnectionConfig _config;

        private bool _disposed;
        private System.Net.Sockets.Socket _clientSocket;

        /// <inheritdoc cref="EventInvoker"/>
        protected EventInvoker() { }

        /// <inheritdoc cref="EventInvoker"/>
        public EventInvoker(ConnectionConfig config)
        {
            Check.NotNull(config, nameof(config));

            _config = config;

            if (config.ConnectMode == ConnectMode.Long)
            {
                _clientSocket = CreateSocket();
            }
        }

        /// <summary>
        /// 创建一个新的套接字
        /// </summary>
        /// <returns></returns>
        private System.Net.Sockets.Socket CreateSocket()
        {
            var clientSocket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.SendBufferSize = _config.SendBufferSize;
            clientSocket.ReceiveBufferSize = _config.ReceiveBufferSize;
            clientSocket.SendTimeout = _config.SendTimeout;
            clientSocket.ReceiveTimeout = _config.ReceiveTimeout;

            return clientSocket;
        }

        /// <inheritdoc/>
        public async Task<SocketResponse> SendAsync(byte[] input, int timeout = -1, CancellationToken cancellationToken = default)
        {
            return await InnertSendAsync(input, timeout, cancellationToken);
        }

        private async Task<SocketResponse> InnertSendAsync(byte[] input, int timeout = -1, CancellationToken cancellationToken = default)
        {
            _clientSocket = this._clientSocket ?? CreateSocket();

            var connectTask = ConnectAsync(_clientSocket, new IPEndPoint(IPAddress.Parse(_config.ServerAddress), _config.ServerPort), timeout, cancellationToken);

            var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout, cancellationToken));

            if (completedTask != connectTask)
            {
                throw new TimeoutException($"Socket连接超时，超时时间限定：{timeout} 毫秒");
            }

            await connectTask;

            byte[] dataBytes = input;// Config.Encoding.GetBytes(JsonSerializer.Serialize(input, JsonSerializerOptions));

            var sendEventArgs = new SocketAsyncEventArgs();
            sendEventArgs.SetBuffer(dataBytes, 0, dataBytes.Length);

            int maxBufferSize = _config.MaxBufferSize;// 0x7FEFFFFF;//2GB,数组大小最大是2GB//address.BufferSize
            var receiveEventArgs = new SocketAsyncEventArgs();
            receiveEventArgs.SetBuffer(new byte[maxBufferSize], 0, maxBufferSize);

            var receiveTask = ReceiveAsync(_clientSocket, receiveEventArgs, timeout, cancellationToken);
            var sendTask = SendAsync(_clientSocket, sendEventArgs, timeout, cancellationToken);

            await Task.WhenAll(sendTask, receiveTask);

            byte[] datas = receiveTask.Result;//receiveEventArgs.Buffer
            string responseData = _config.Encoding.GetString(datas, 0, receiveEventArgs.BytesTransferred);
            //Debug.WriteLine($"Socket返回的结果：{responseData}");

            SocketResponse result = SocketResponse.Success(datas, encoding: _config.Encoding);

            if (_config.ConnectMode == ConnectMode.Short)
            {
                Close();
            }

            return result;
        }

        private Task ConnectAsync(System.Net.Sockets.Socket socket, EndPoint endPoint, int timeout = 4000, CancellationToken cancellationToken = default)
        {
            var tcs = new TaskCompletionSource<bool>();

            cancellationToken.Register(() => tcs.TrySetCanceled());

            SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs
            {
                RemoteEndPoint = endPoint
            };

            connectEventArgs.Completed += (s, e) =>
            {
                if (e.SocketError == SocketError.Success)
                {
                    tcs.TrySetResult(true);
                }
                else
                {
                    tcs.TrySetException(new SocketException((int)e.SocketError));
                }
            };

            if (!socket.ConnectAsync(connectEventArgs))
            {
                tcs.TrySetResult(true);
            }

            return Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).Unwrap();
        }

        private Task SendAsync(System.Net.Sockets.Socket socket, SocketAsyncEventArgs sendEventArgs, int timeout = 4000, CancellationToken cancellationToken = default)
        {
            var tcs = new TaskCompletionSource<int>();

            cancellationToken.Register(() => tcs.TrySetCanceled());

            sendEventArgs.Completed += (s, e) =>
            {
                if (e.SocketError == SocketError.Success)
                {
                    tcs.TrySetResult(e.BytesTransferred);
                }
                else
                {
                    tcs.TrySetException(new SocketException((int)e.SocketError));
                }
            };

            if (!socket.SendAsync(sendEventArgs))
            {
                tcs.TrySetResult(sendEventArgs.BytesTransferred);
            }

            return Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).Unwrap();
        }

        private async Task<byte[]> ReceiveAsync(System.Net.Sockets.Socket socket, SocketAsyncEventArgs receiveEventArgs, int timeout = 4000, CancellationToken cancellationToken = default)
        {
            var tcs = new TaskCompletionSource<int>();

            cancellationToken.Register(() => tcs.TrySetCanceled());

            List<byte> receivedData = new List<byte>();

            receiveEventArgs.Completed += (s, e) =>
            {
                if (e.SocketError == SocketError.Success)
                {
                    if (e.BytesTransferred > 0)
                    {
                        receivedData.AddRange(e.Buffer.Take(e.BytesTransferred));
                        tcs.TrySetResult(e.BytesTransferred);
                    }
                    else
                    {
                        tcs.TrySetResult(0);
                    }
                }
                else
                {
                    //Debug.WriteLine($"No data received. SocketError: {e.SocketError}");

                    //if (socket.Connected)
                    //{
                    //    // 检查缓冲区中是否有可用数据
                    //    if (socket.Available == 0)
                    //    {
                    //        Debug.WriteLine("缓存中没有可用数据");
                    //        // 可能是远程端尚未发送数据
                    //    }
                    //}
                    //else
                    //{
                    //    Debug.WriteLine("连接已被远程主机关闭");
                    //    // 可以在这里添加其他处理，例如关闭本地连接等
                    //}

                    tcs.TrySetException(new SocketException((int)e.SocketError));
                }
            };

            if (!socket.ReceiveAsync(receiveEventArgs))
            {
                tcs.TrySetResult(receiveEventArgs.BytesTransferred);
            }

            //Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).Unwrap();

            //取字节数据长度
            int bytesRead = await tcs.Task;
            //return bytesRead;

            //取字节数据
            byte[] receivedBytes = receivedData.ToArray();

            //byte[] receivedBytes = new byte[bytesRead];
            //Buffer.BlockCopy(receiveEventArgs.Buffer, 0, receivedBytes, 0, bytesRead);

            return receivedBytes;
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Close()
        {
            _clientSocket?.Close();
            _clientSocket?.Dispose();
            _clientSocket = null;
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                Close();
            }

            _disposed = true;
        }
    }
}